use async_trait::async_trait;
use std::sync::Arc;

use my_rust_core::{
    constants,
    shared::{argon2_encryption, date_helper, util},
};

use sea_orm::{
    ActiveModelTrait, DatabaseConnection, DbErr, Set, TransactionError, TransactionTrait,
};

use super::role_service::RoleService;
use crate::entities;

#[async_trait]
pub trait AccountGroupService: Send + Sync {
    /// Creates a new AccountGroup and a new User.
    /// This typically used by new user register.
    /// During register the username and password are only needed.
    /// The other details can be added later.
    async fn register(&self, username: &str, password: &str, full_name: &str) -> Result<(), DbErr>;
}

pub struct AccountGroupServiceImpl {
    db: Arc<DatabaseConnection>,
    role_service: Arc<dyn RoleService>,
}

impl AccountGroupServiceImpl {
    pub fn new(db: Arc<DatabaseConnection>, role_service: Arc<dyn RoleService>) -> Self {
        AccountGroupServiceImpl {
            db: db.clone(),
            role_service,
        }
    }
}

#[async_trait]
impl AccountGroupService for AccountGroupServiceImpl {
    /// Creates a new AccountGroup and a new User.
    /// This typically used by new user register.
    /// During register the username and password are only needed.
    /// The other details can be added later.
    async fn register(&self, username: &str, password: &str, full_name: &str) -> Result<(), DbErr> {
        let role_id = self
            .role_service
            .query_id(constants::role::ROLE_ACCOUNT_GROUP_ADMIN)
            .await?
            .expect(
                format!(
                    "Role id NOT found for {}",
                    constants::role::ROLE_ACCOUNT_GROUP_ADMIN,
                )
                .as_str(),
            );

        let username = username.to_string();
        let password = password.to_string();
        let full_name = full_name.to_string();

        // Perform a transaction with a closure. The transaction will be committed if the closure returned Ok, rollbacked if returned Err.
        // The 2nd and 3rd type parameters are the Ok and Err types respectively. Since async_closure is not yet stabilized, you have to Pin<Box<_>> it.
        // https://www.sea-ql.org/SeaORM/docs/advanced-query/transaction/
        //
        // To perform a set of operations within a transaction.
        self.db
            .transaction::<_, (), DbErr>(|txn| {
                Box::pin(async move {
                    // Creates a new AccountGroup which will be used by the new User.
                    let account_group = entities::account_group::ActiveModel {
                        id: Set(util::generate_uuid_as_hex_string()),
                        title: Set("我的账户".to_string()),
                        date_created: Set(date_helper::get_current_naive_date_time()),
                        last_updated: Set(date_helper::get_current_naive_date_time()),
                    }
                    .insert(txn)
                    .await?;

                    // This action is highly time-consuming. Because Argon2 is a memory- and CPU-intensive hashing algorithm designed specifically to slow down brute-force attacks.
                    let password_hash = argon2_encryption::hash_password(&password);

                    // Creates a new User with the created AccountGroup and username, password.
                    // Assigns the default role to the created user.
                    let user = entities::user::ActiveModel {
                        id: Set(util::generate_uuid_as_hex_string()),
                        account_group_id: Set(Some(account_group.id.clone())),
                        username: Set(username),
                        password: Set(password_hash),
                        full_name: Set(full_name),
                        enabled: Set(true),
                        password_expired: Set(false),
                        account_expired: Set(false),
                        auth_failed_attempt: Set(0),
                        account_locked: Set(false),
                        date_created: Set(date_helper::get_current_naive_date_time()),
                        last_updated: Set(date_helper::get_current_naive_date_time()),
                        ..Default::default()
                    }
                    .insert(txn)
                    .await?;

                    // Defines relationship between account group and user.
                    let _ = entities::account_group_user::ActiveModel {
                        id: Set(util::generate_uuid_as_hex_string()),
                        account_group_id: Set(account_group.id.clone()),
                        user_id: Set(user.id.clone()),
                        is_account_group_creator: Set(true),
                    }
                    .insert(txn)
                    .await?;

                    // Assigns the default role to the created user.
                    let _ = entities::user_role::ActiveModel {
                        id: Set(util::generate_uuid_as_hex_string()),
                        account_group_id: Set(account_group.id.clone()),
                        user_id: Set(user.id.clone()),
                        role_id: Set(role_id),
                    }
                    .insert(txn)
                    .await?;

                    Ok(())
                })
            })
            .await
            .map_err(|err| match err {
                TransactionError::Connection(e) => e, // extract the inner `DbErr`
                TransactionError::Transaction(e) => e, // for transaction-level errors
            })?;

        Ok(())
    }
}
